/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#ifndef __COMMAND_LINE_HH__
#define __COMMAND_LINE_HH__

#include <cstring>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <utility>

namespace playerpiano
{
  std::map<std::string, const char*>
  cl_parse (int                                 argc,
            char**                              argv,
            const std::set<std::string>&        takes_arg)
    {
      std::map<std::string, const char*> rv;

      while (argc > 1)
        {
          if (takes_arg.find (argv[1]) == takes_arg.end ())
            {
              rv[std::string (argv[1])] = 0;
            }
          else 
            {
              rv[std::string (argv[1])] = (argc > 2) ? argv[2] : 0;

              ++argv;
              --argc;
            }

          ++argv;
          --argc;
        }

      return rv;
    }

  struct NonEmptyString { };
  struct ConstantString
    {
      const char* string;

      ConstantString (const char* _string) : string (_string) { }
    };
  struct RealNumber
    { 
      float min;
      float max;

      RealNumber (float _min)
        : min (_min),
          max (std::numeric_limits<float>::max ())
        {
        }

      RealNumber (float _min,
                  float _max) 
        : min (_min),
          max (_max) 
        { 
        }
    };

  struct PositiveInteger 
    { 
      unsigned int min;
      unsigned int max;

      PositiveInteger (unsigned int _min)
        : min (_min),
          max (std::numeric_limits<unsigned int>::max ())
        {
        }

      PositiveInteger (unsigned int _min,
                       unsigned int _max) 
        : min (_min),
          max (_max) 
        { 
        }
    };
  struct FloatArray
    {
      unsigned int n;
      FloatArray (const char* v) : n (v ? strtoul (v, 0, 0) : 0) { }
    };

  template<typename T>
  bool
  enforce_spec (const char*                     name,
                const char*                     value,
                T);

  template<>
  bool
  enforce_spec (const char*                     name,
                const char*                     orig_value,
                FloatArray                      spec)
    {
      const char* value = orig_value;

      for (unsigned int n = 0; n < spec.n; ++n)
        {
          const char* comma = strchr (value, ',');

          if (! comma && n + 1 < spec.n)
            { 
              std::cerr << "ERROR: not enough comma-separated parameters in '"
                        << orig_value
                        << "' for argument '"
                        << name
                        << "'" 
                        << std::endl;

              return true;
            }

          if (comma && n + 1 >= spec.n)
            { 
              std::cerr << "ERROR: too many comma-separated parameters in '"
                        << orig_value
                        << "' for argument '"
                        << name
                        << "'" 
                        << std::endl;

              return true;
            }

          char* endptr;
          float val = strtof (value, &endptr);

          if ((comma && endptr != comma) ||
              (! comma && *endptr != '\0') ||
              (val <= 0))
            {
              std::cerr << "ERROR: invalid float starting at '"
                        << value
                        << "' for argument '"
                        << name
                        << "'"
                        << std::endl;

              return true;
            }

          value = comma + 1;
        }

      return false;
    }

  template<>
  bool
  enforce_spec (const char*                     name,
                const char*                     value,
                NonEmptyString)
    {
      if (strlen (value) == 0)
        {
          std::cerr << "ERROR: invalid non-empty string value '"
                    << value
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      return false;
    }

  template<>
  bool
  enforce_spec (const char*                     name,
                const char*                     value,
                ConstantString                  constant)
    {
      if (strcmp (value, constant.string) != 0)
        {
          std::cerr << "ERROR: invalid string value '"
                    << value
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      return false;
    }

  template<>
  bool
  enforce_spec (const char*                     name,
                const char*                     value,
                PositiveInteger                 spec)
    {
      char* endptr;
      unsigned int val = strtoul (value, &endptr, 0);

      if (*value == '\0' || *endptr != '\0')
        {
          std::cerr << "ERROR: invalid positive integer value '"
                    << value
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      if (val < spec.min)
        {
          std::cerr << "ERROR: integer value '"
                    << value
                    << "' is less than minimum allowed value '"
                    << spec.min 
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      if (val > spec.max)
        {
          std::cerr << "ERROR: integer value '"
                    << value
                    << "' is greater than maximum allowed value '"
                    << spec.max
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      return false;
    }

  template<>
  bool
  enforce_spec (const char*                     name,
                const char*                     value,
                RealNumber                      spec)
    {
      char* endptr;
      float val = strtof (value, &endptr);

      if (*value == '\0' || *endptr != '\0')
        {
          std::cerr << "ERROR: invalid real number value '"
                    << value
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      if (val < spec.min)
        {
          std::cerr << "ERROR: real number value '"
                    << value
                    << "' is less than minimum allowed value '"
                    << spec.min 
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      if (val > spec.max)
        {
          std::cerr << "ERROR: real number value '"
                    << value
                    << "' is greater than maximum allowed value '"
                    << spec.max
                    << "' for argument '"
                    << name
                    << "'"
                    << std::endl;

          return true;
        }

      return false;
    }

  template<typename Spec>
  struct ArgSpec
    {
      const char* name;
      Spec        spec;

      ArgSpec (const char* _name,
               Spec        _spec)
        : name (_name),
          spec (_spec)
        {
        }
    };

  template<typename T>
  struct DefaultValue
    {
      const char*       name;
      const char*       default_value;
      T                 payload;

      DefaultValue (const char* _name,
                    const char* _default_value,
                    T           _payload)
        : name (_name),
          default_value (_default_value),
          payload (_payload)
        {
        }
    };

  struct Null
    {
    };

  template<typename T>
  struct ArgBuilder
    {
      T value;

      ArgBuilder (T _value) : value (_value) { }

      template<typename U>
      ArgBuilder<std::pair<ArgSpec<U>, T> >
      add (const char* name,
           U           spec)
        {
          ArgSpec<U> arg_spec (name, spec);

          return 
            ArgBuilder<std::pair<ArgSpec<U>, T> > 
              (std::make_pair (arg_spec, value));
        }
    };

  template<typename A,
           typename B>
  struct ArgBuilder<std::pair<ArgSpec<A>, B> >
    {
      typedef std::pair<ArgSpec<A>, B> T;

      T value;

      ArgBuilder (T _value) : value (_value) { }

      template<typename U>
      ArgBuilder<std::pair<ArgSpec<U>, T> >
      add (const char* name,
           U           spec)
        {
          ArgSpec<U> arg_spec (name, spec);

          return 
            ArgBuilder<std::pair<ArgSpec<U>, T> > 
              (std::make_pair (arg_spec, value));
        }

      ArgBuilder<std::pair<DefaultValue<ArgSpec<A> >, B> >
      default_value (const char* dv)
        {
          return 
            ArgBuilder<std::pair<DefaultValue<ArgSpec<A> >, B> >
              (std::make_pair (DefaultValue<ArgSpec<A> > (value.first.name,
                                                          dv,
                                                          value.first),
                               value.second));
        }
    };

  ArgBuilder<Null>
  arg_list ()
    {
      return ArgBuilder<Null> (Null ());
    }

  template<typename T>
  bool
  required (std::map<std::string, const char*>& args,
            T                                   spec);

  template<typename T>
  bool
  required (std::map<std::string, const char*>& args,
            DefaultValue<T>                     spec)
    {
      if (! args[spec.name])
        {
          args[spec.name] = spec.default_value;
        }

      return required (args, spec.payload);
    }

  template<typename T>
  bool
  required (std::map<std::string, const char*>& args,
            ArgSpec<T>                          spec)
    {
      if (! args[spec.name])
        {
          std::cerr << "ERROR: missing required command line argument '" 
                    << spec.name
                    << "'" 
                    << std::endl;

          std::cerr << "try --help" << std::endl;

          return true;
        }

      return enforce_spec (spec.name, args[spec.name], spec.spec);
    }

  template<typename Head,
           typename Tail>
  bool
  required (std::map<std::string, const char*>& args,
            std::pair<Head, Tail>               spec)
    {
      return required (args, spec.first) || required (args, spec.second);
    }

  template<>
  bool
  required (std::map<std::string, const char*>&,
            Null)
    {
      return false;
    }
}

#endif // __COMMAND_LINE_HH__
